package org.zoomdev.zoom.dao.configs;

import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.SqlBuilder;
import org.zoomdev.zoom.dao.SqlConfig;
import org.zoomdev.zoom.dao.driver.SqlDriver;
import org.zoomdev.zoom.dao.entity.Entity;
import org.zoomdev.zoom.dao.entity.Field2ColumnMappingConfig;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.zoomdev.zoom.dao.SqlBuilder.AND;
import static org.zoomdev.zoom.dao.SqlBuilder.OR;

public final class Where {
    private final List<Object> values = new ArrayList<>(10);
    private final StringBuilder where = new StringBuilder(128);

    private final SqlDriver driver;
    private final SqlConfig config;
    private Entity entity;
    private boolean condition;
    private String table;

    public Where(SqlDriver driver, SqlConfig config, Entity entity) {
        this.driver = driver;
        this.config = config;
        this.entity = entity;
    }

    public void append(StringBuilder sql) {
        if (where.length() > 0) {
            sql.append(" WHERE ");
            sql.append(where);
        }
    }

    public int setParameter(PreparedStatement stmt, int index) throws SQLException {
        for (Object value : values) {
            stmt.setObject(index++, value);
        }
        return index;
    }

    public void clear() {
        values.clear();
        where.setLength(0);
    }


    public void whereNotNull(String name) {
        whereNull(name, AND, true);
    }

    public void whereNull(String name) {
        whereNull(name, AND, false);
    }

    public void where(String name, Object value) {
        whereImpl(where, name, Symbol.EQ, value, AND);
    }

    public void addValue(Field2ColumnMappingConfig adapter, Object value) {
        values.add(value);
    }

    public void where(Field2ColumnMappingConfig config, Object value) {
        StringBuilder where = this.where;
        String columnName = config.getColumnName();
        checkValue(columnName, value);
        whereRelation(where, AND);
        driver.protectColumn(where, columnName).append(Symbol.EQ.value()).append("?");
        addValue(config, value);
    }

    public void where(String name, Symbol symbol, Object value) {
        assert (name != null && symbol != null);
        whereImpl(where, name, symbol, value, AND);
    }

    public void orWhere(String name, Symbol symbol, Object value) {
        assert (name != null && symbol != null);
        whereImpl(where, name, symbol, value, OR);
    }

    public void orLike(String name, SqlBuilder.Like like, Object value) {
        relationLike(name, like, value, OR, false);
    }

    public void notLike(String name, SqlBuilder.Like like, Object value) {
        relationLike(name, like, value, AND, false);
    }

    public void like(String name, SqlBuilder.Like like, Object value) {
        relationLike(name, like, value, AND, true);
    }

    public String getTable() {
        return table;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public List<Object> getValues() {
        return values;
    }

    public void setEntity(Entity entity) {
        this.entity = entity;
    }

    public boolean hasCondition() {
        return where.length() > 0;
    }

    public <E> void whereIn(String name, Collection<E> values) {
        whereIn(name, AND, false, values);
    }

    private void whereNull(String name, String relation, boolean not) {
        Field2ColumnMappingConfig config = getConfig(name);
        StringBuilder where = this.where;
        whereRelation(where, relation);
        driver.protectColumn(where, config.getColumnName());
        where.append(" IS ");
        if (not) where.append("NOT ");
        where.append("NULL");
    }

    private Field2ColumnMappingConfig getConfig(String name) {
        Field2ColumnMappingConfig config = entity.getConfig(name);
        if (config == null) {
            throw new DaoException("找不到" + name + "配置");
        }
        return config;
    }

    private void whereRelation(StringBuilder where, String relation) {
        if (condition) {
            condition = false;
        } else {
            if (where.length() > 0) {
                where.append(relation);
            }
        }
    }

    private void checkValue(String name, Object value) {
        if (value == null) {
            throw new DaoException(String.format("名称为%s的字段值为null,请使用whereNull或者whereNotNull版本", name));
        }
    }

    private void relationLike(String name, SqlBuilder.Like like, Object value, String relation, boolean not) {
        assert (name != null);

        checkValue(name, value);
        whereRelation(where, relation);

        Field2ColumnMappingConfig config = getConfig(name);
        driver.protectColumn(where, config.getColumnName());
        if (not) {
            where.append(" NOT");
        }
        where.append(" LIKE ?");
        addValue(config, like.toValue(value));
    }

    private <E> void whereIn(String name, String relation, boolean not, Collection<E> values) {
        StringBuilder where = this.where;
        whereRelation(where, relation);
        Field2ColumnMappingConfig config = getConfig(name);
        driver.protectColumn(where, config.getColumnName());
        if (not) {
            where.append(" NOT");
        }
        where.append(" IN (");
        boolean first = true;
        for (Object object : values) {
            if (first) {
                first = false;
            } else {
                where.append(",");
            }
            where.append("?");
            this.addValue(config, object);
        }
        where.append(')');
    }

    void whereImpl(StringBuilder where, String name, Symbol symbol, Object value, String relation) {
        Field2ColumnMappingConfig config = getConfig(name);
        if (entity.getSplitColumn() == config) {
            this.config.setTable(entity.getSplitTableRule().getTable(value));
        }

        String columnName = config.getColumnName();
        checkValue(columnName, value);
        whereRelation(where, relation);
        driver.protectColumn(where, columnName).append(symbol.value()).append("?");
        addValue(config, value);
    }


}
